Skip to content

feat(decision-logs): apply the mask_decision policy to events - #186

Open
sspaink wants to merge 1 commit into
open-policy-agent:mainfrom
sspaink:feat/decision-log-mask
Open

feat(decision-logs): apply the mask_decision policy to events#186
sspaink wants to merge 1 commit into
open-policy-agent:mainfrom
sspaink:feat/decision-log-mask

Conversation

@sspaink

@sspaink sspaink commented Aug 6, 2026

Copy link
Copy Markdown
Member

Fixes #73

Implements decision_logs.mask_decision: decision events are redacted before they are buffered, uploaded, or written to the console. MaskRuleSet ports v1/plugins/logs/mask.go, and a masking failure drops the event rather than logging it unmasked.

The mask policy has to be built as a plan entrypoint (opa build -t plan -e system/log/mask), since this SDK evaluates compiled plans. A configured entrypoint with no plan logs a warning.

Decision events are now redacted by the policy at
decision_logs.mask_decision (default system/log/mask) before they are
buffered, uploaded, or written to the console.

MaskRuleSet ports plugins/logs/mask.go: the shorthand string form and the
structured {op, path, value} form, url.PathUnescape/PathEscape segment
handling, Go's silent skipping of undefined paths, and the bookkeeping of
applied rules in the event's erased/masked arrays. A mask policy that
fails to prepare, evaluate, or parse drops the event rather than logging
it unmasked, matching OPA Go. The prepared query is cached until a bundle
is activated, as Go caches until its compiler is updated.

Because this SDK evaluates compiled IR plans rather than running a
compiler, the mask policy has to be built as an entrypoint
(opa build -t plan -e system/log/mask). When an explicitly configured
entrypoint has no plan, masking is inactive and says so with a warning.

IR plans wrap results as {"result": <value>}. That envelope is now
stripped before the decision is returned or logged, so mask rules address
/result/<field> and callers get the decision value itself -- the shape
both READMEs already document.

Fixes open-policy-agent#73

BREAKING CHANGE: Opa.DecisionResult.getResult() returns the decision
value instead of the {"result": <value>} envelope. Callers reading
getResult().get("result") should read getResult() directly;
getResult().asBoolean() and getResultAs(Class) now behave as documented.

Signed-off-by: Sebastian Spaink <sebastianspaink@gmail.com>
@sspaink
sspaink marked this pull request as ready for review August 6, 2026 19:08
@sspaink
sspaink requested a review from a team as a code owner August 6, 2026 19:08

@kroekle kroekle left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks really good. Maybe just a docs update and a new test and I think it's good to go.

// TODO: Apply drop decision policy if configured

if (!applyMask(event)) {
return; // masking failed: drop the event rather than log it unmasked, as OPA Go does

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is good for now, but maybe we need to start a conversation on the Go OPA side. This seems to me to be that should be configurable.

return ((ObjectNode) node).remove(target) != null;
}
if (node.isArray()) {
// Deliberate divergence: for an array-valued field ("/input/1" with an array input), Go

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should add this into the readme somewhere as well.

* Tests for applying the configured {@code mask_decision} policy to decision log events. The mask
* policies come from the compiled plans in {@code src/test/resources/mask}.
*/
class DecisionLogPluginMaskTest {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I missed it then you can close this, but I think we need a test that does has conflicting rules, say we have a rule to mask and remove an attribute. I think order is going to be important.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement mask_decision policy

2 participants